# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

from coin.exchange.binance.kr_rest.product import BinanceProduct
from coin.exchange.shared.kr_rest.product import SharedCurrency
from coin.exchange.kr_rest.product.product_impl import generate_product_from_subreq
from coin.proto.coin_executor_pb2 import PassiveExecutorConfig
from coin.strategy.executor.passive.util import check_executor_config
from coin.exchange.base.kr_rest.futures_product import NormFuturesProduct
from coin.strategy.accounting.pnl_balance_printer.util import reserve_as_proto


class L2Config(object):
  def __init__(self,
               key,
               *,
               parent,
               native_products,
               min_pos,
               max_pos,
               edge_bp,
               lot_size,
               agg_period,
               order_update_period,
               min_posting_period,
               max_posting_period,
               fill_cooldown_period,
               ema_lifetime_sec,
               true_book_price_pass,
               true_book_qty_pass,
               true_book_qty_ref,
               edge_agg,
               fee_pass,
               leaning,
               vwap_width_sec,
               volume_width_sec,
               target_offset,
               target_intensity):
    self.parent = parent
    self.key = key
    self.reserve_balance = (max_pos + min_pos) / 2

    self.edge_agg = edge_agg
    self.agg_period = agg_period

    self.min_pos = min_pos
    self.max_pos = max_pos
    self.lot_size = lot_size
    self.order_update_period = order_update_period
    self.min_posting_period = min_posting_period
    self.max_posting_period = max_posting_period
    self.fill_cooldown_period = fill_cooldown_period
    self.target_offset = target_offset
    self.target_intensity = target_intensity

    self.fee_pass = fee_pass

    # True book
    assert any((true_book_price_pass, true_book_qty_pass))
    self.true_book_price_pass = true_book_price_pass
    self.true_book_qty_pass = true_book_qty_pass
    self.true_book_qty_ref = true_book_qty_ref

    # Optional
    self.edge = edge_bp * 1e-4 if edge_bp is not None else None
    self.ema_lifetime_sec = ema_lifetime_sec
    self.vwap_width_sec = vwap_width_sec
    self.volume_width_sec = volume_width_sec
    self.leaning = leaning

    self.native_products = native_products

    for native_product in native_products:
      assert key == native_product.base.currency, (key, native_product.base)

  def agg_enabled(self):
    return self.agg_period is not None

  def executor_config(self, symbol):
    proto_config = PassiveExecutorConfig()
    proto_config.symbol = symbol  # TODO(inkyu): Really here?
    proto_config.lot_size = self.lot_size
    proto_config.min_pos = self.min_pos
    proto_config.max_pos = self.max_pos
    proto_config.max_posting_period = self.max_posting_period
    proto_config.min_posting_period = self.min_posting_period
    proto_config.order_update_period = self.order_update_period
    proto_config.fill_cooldown_period = self.fill_cooldown_period
    check_executor_config(proto_config)
    return proto_config


class L1Config(object):
  def __init__(self, *, exchanges, config, subreq0, subreq1):
    self.subreq = subreq0
    self.exchanges = exchanges

    self.is_futures = exchanges[0].startswith('Futures')

    common_config = config['common']
    master_config = config['master']

    self.quote_currencies = [
        master_config['quote_currency_0'],
        master_config['quote_currency_1'],
    ]
    # TODO(inkyu): Separate
    self.ratio_min_balance = master_config['ratio_min_balance']
    self.ratio_max_balance = master_config['ratio_max_balance']
    self.bnb_reserve = master_config.get('bnb_reserve')
    self.key_memcached = master_config.get('key_memcached')
    self.okdk_value = master_config.get('okdk_value')

    self.value_on_usdt_0 = master_config.get('value_on_usdt_0', None)
    self.value_on_usdt_1 = master_config.get('value_on_usdt_1', None)

    if self.bnb_reserve is not None:
      for exchange_id, exchange in enumerate(self.exchanges):
        splitted = exchange.split('.')
        if len(splitted) >= 2 and splitted[1] == 'Binance':
          break
      else:
        raise ValueError('`bnb_reserve` is set, but we are not trading Binance.')

      quote = master_config['quote_currency_%s' % exchange_id]
      self.extra_product = BinanceProduct.FromStrPair('BNB', quote)
      self.extra_product.exchange_id = exchange_id
    else:
      self.extra_product = None

    self._l2_configs = {}
    for key, product_config in config['products'].items():
      config = {**common_config, **product_config}

      trade_symbol = config.get('trade_symbol')
      if trade_symbol is None:
        trade_symbol = '%s-%s' % (key, self.quote_currencies[0])
      product0 = generate_product_from_subreq(subreq0, trade_symbol)

      ref_symbol = config.get('ref_symbol')
      if ref_symbol is None:
        ref_symbol = '%s-%s' % (key, self.quote_currencies[1])
      product1 = generate_product_from_subreq(subreq1, ref_symbol)

      self._l2_configs[key] = L2Config(
          key,
          parent=self,
          native_products=[product0, product1],
          min_pos=config['min_pos'],
          max_pos=config['max_pos'],
          lot_size=config['lot_size'],
          edge_agg=config['edge_agg_bp'] * 1e-4,
          agg_period=int(config['agg_period_sec'] * 1e9),
          order_update_period=int(config['order_update_period_sec'] * 1e9),
          min_posting_period=int(config.get('min_posting_period_sec', 0.2) * 1e9),
          max_posting_period=int(config.get('max_posting_period_sec', 20.) * 1e9),
          fill_cooldown_period=int(config.get('fill_cooldown_period_sec', 2.) * 1e9),
          edge_bp=config.get('edge_bp', None),
          ema_lifetime_sec=config.get('ema_lifetime_sec', None),
          true_book_price_pass=config.get('true_book_price_pass'),
          true_book_qty_pass=config.get('true_book_qty_pass'),
          true_book_qty_ref=config.get('true_book_qty_ref', None),
          leaning=config.get('leaning'),
          fee_pass=config['fee_pass_bp'] * 1e-4,
          vwap_width_sec=config['vwap_width_sec'],
          volume_width_sec=config['volume_width_sec'],
          target_offset=config.get('target_offset'),
          target_intensity=config.get('target_intensity'),
      )

  def reserve_map(self):
    return {key: l2_config.reserve_balance for key, l2_config in self._l2_configs.items()}

  def reserve_proto(self):
    return reserve_as_proto(self.subreq, self.reserve_map())

  def get_native_products(self, idx):
    return [l2_config.native_products[idx] for l2_config in self._l2_configs.values()]

  def get_currencies(self, idx):
    products = self.get_native_products(idx)
    base_currencies = {product.base for product in products}
    quote_currencies = {product.quote for product in products}
    return list(base_currencies | quote_currencies)

  def get_l2(self, key):
    return self._l2_configs[key]

  def keys(self):
    return self._l2_configs.keys()

  def items(self):
    return self._l2_configs.items()

  def get_base_currencies(self):
    return [SharedCurrency.FromStrCurrency(key) for key in self._l2_configs.keys()]
